﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Linq;
using WCFCollection;
using System.Xml;
using System.Reflection;

namespace RemoteLinqBase
{
    [DataContract(Name = "ArrayOfAnonymousSurrogatedList", Namespace = "")]
    internal class AnonymousSurrogatedList
    {
        [DataMember(Name="AnonymousData")]
        public XmlNode[] XmlData { get; set; }
    }

    [DataContract(Namespace = "")]
    internal class AnonymousSurrogated
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="AnonymousSurrogated"/> class.
        /// </summary>
        public AnonymousSurrogated()
        {
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="AnonymousSurrogated"/> class.
        /// </summary>
        /// <param name="data">The data.</param>
        public AnonymousSurrogated(XElement data)
        {
            XmlData = DataContractSurrogate.GetXmlNode(data);
        }
        [DataMember]
        public XmlElement XmlData { get; set; }
    }

    public class DataContractSurrogate : IDataContractSurrogate
    {
        /// <summary>
        /// Converts XmlNode to XElement.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <returns>XElement</returns>
        public static XElement GetXElement(XmlNode node)
        {
            XDocument xDoc = new XDocument();
            using (XmlWriter xmlWriter = xDoc.CreateWriter())
                node.WriteTo(xmlWriter);
            return xDoc.Root;
        }

        /// <summary>
        /// Converts XElement to XmlNode.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <returns>XmlNode</returns>
        public static XmlElement GetXmlNode(XElement element)
        {
            using (XmlReader xmlReader = element.CreateReader())
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
                return xmlDoc.DocumentElement;
            }
        }
        /// <summary>
        /// Gets the start type for this deserializer.
        /// </summary>
        /// <value>The start type.</value>
        public Type StartType { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataContractSurrogate"/> class.
        /// </summary>
        /// <param name="startType">The start type.</param>
        public DataContractSurrogate(Type startType)
        {
            StartType = startType;
        }

        /// <summary>
        /// During serialization, deserialization, and schema import and export, returns a data contract type that substitutes the specified type.
        /// </summary>
        /// <param name="type">The CLR type <see cref="T:System.Type"/> to substitute.</param>
        /// <returns>
        /// The <see cref="T:System.Type"/> to substitute for the <paramref name="type"/> value. This type must be serializable by the <see cref="T:System.Runtime.Serialization.DataContractSerializer"/>. For example, it must be marked with the <see cref="T:System.Runtime.Serialization.DataContractAttribute"/> attribute or other mechanisms that the serializer recognizes.
        /// </returns>
        public Type GetDataContractType(Type type)
        {
            if (type.Name.StartsWith("<>"))
            {
                return typeof(AnonymousSurrogated);
            }
            if (type.IsGenericType && type.GetGenericArguments()[0].Name.StartsWith("<>"))
            {
                return typeof(AnonymousSurrogatedList);
            }
            return type;
        }

        private static Type LocateType(Type startType, string typeName)
        {
            if (startType.Name == typeName)
            {
                return startType;
            }
            if (startType.IsGenericType)
            {
                foreach (var argumentType in startType.GetGenericArguments())
                {
                    if (argumentType.Name == typeName)
                    {
                        return argumentType;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// During deserialization, returns an object that is a substitute for the specified object.
        /// </summary>
        /// <param name="obj">The deserialized object to be substituted.</param>
        /// <param name="targetType">The <see cref="T:System.Type"/> that the substituted object should be assigned to.</param>
        /// <returns>
        /// The substituted deserialized object. This object must be of a type that is serializable by the <see cref="T:System.Runtime.Serialization.DataContractSerializer"/>. For example, it must be marked with the <see cref="T:System.Runtime.Serialization.DataContractAttribute"/> attribute or other mechanisms that the serializer recognizes.
        /// </returns>
        public object GetDeserializedObject(object obj, Type targetType)
        {
            if (obj.GetType() == typeof(AnonymousSurrogatedList))
            {
                Type anonymousType = StartType.GetGenericArguments()[0];
                List<object> values = new List<object>();
                foreach (var item in (obj as AnonymousSurrogatedList).XmlData)
                {
                    values.Add(SerializationTool.ToObject(anonymousType, GetXElement(item)));
                }

                var result = Activator.CreateInstance(typeof(List<>).MakeGenericType(anonymousType));
                MethodInfo addNew = result.GetType().GetMethod("Add");
                foreach (var value in values)
                {
                    addNew.Invoke(result, new object[] { value });
                }

                return result;
            }
            else if (obj.GetType() == typeof(AnonymousSurrogated))
            {
                XElement data = GetXElement((obj as AnonymousSurrogated).XmlData);
;
                XElement[] dataArguments = data.Nodes().Select(node => node as XElement).ToArray();
                string anonymousType = string.Format("{0}`{1}", data.Name.LocalName.Replace("_x003C__x003E_", "<>"), dataArguments.Length);
                Type objectType = LocateType(StartType, anonymousType);
                if (objectType == null)
                {
                    throw new Exception("Anonymous types in anonymous types not yet supported!");
                }
                List<object> arguments = new List<object>();
                var genericArguments = objectType.GetGenericArguments();
                for (int i = 0; i < genericArguments.Length; i++)
                {
                    arguments.Add(SerializationTool.ToObject(genericArguments[i], dataArguments[i]));
                }
                return Activator.CreateInstance(objectType, arguments.ToArray());
            }
            return obj;
        }


        /// <summary>
        /// During serialization, returns an object that substitutes the specified object.
        /// </summary>
        /// <param name="obj">The object to substitute.</param>
        /// <param name="targetType">The <see cref="T:System.Type"/> that the substituted object should be assigned to.</param>
        /// <returns>
        /// The substituted object that will be serialized. The object must be serializable by the <see cref="T:System.Runtime.Serialization.DataContractSerializer"/>. For example, it must be marked with the <see cref="T:System.Runtime.Serialization.DataContractAttribute"/> attribute or other mechanisms that the serializer recognizes.
        /// </returns>
        public object GetObjectToSerialize(object obj, Type targetType)
        {
            Type objType = obj.GetType();
            if (targetType == typeof(AnonymousSurrogated))
            {
                var element = new XElement(objType.Name.Replace("<>", "_x003C__x003E_"));
                foreach (var property in objType.GetProperties())
                {
                    element.Add(SerializationTool.ToXML(property.GetValue(obj, null)));
                }
                return new AnonymousSurrogated(element);
            }
            return obj;
        }

        /// <summary>
        /// During schema import, returns the type referenced by the schema.
        /// </summary>
        /// <param name="typeName">The name of the type in schema.</param>
        /// <param name="typeNamespace">The namespace of the type in schema.</param>
        /// <param name="customData">The object that represents the annotation inserted into the XML schema definition, which is data that can be used for finding the referenced type.</param>
        /// <returns>
        /// The <see cref="T:System.Type"/> to use for the referenced type.
        /// </returns>
        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            return null;
        }

        /// <summary>
        /// Processes the type that has been generated from the imported schema.
        /// </summary>
        /// <param name="typeDeclaration">A <see cref="T:System.CodeDom.CodeTypeDeclaration"/> to process that represents the type declaration generated during schema import.</param>
        /// <param name="compileUnit">The <see cref="T:System.CodeDom.CodeCompileUnit"/> that contains the other code generated during schema import.</param>
        /// <returns>
        /// A <see cref="T:System.CodeDom.CodeTypeDeclaration"/> that contains the processed type.
        /// </returns>
        public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
        {
            return typeDeclaration;
        }

        /// <summary>
        /// Sets the collection of known types to use for serialization and deserialization of the custom data objects.
        /// </summary>
        /// <param name="customDataTypes">A <see cref="T:System.Collections.ObjectModel.Collection`1"/>  of <see cref="T:System.Type"/> to add known types to.</param>
        public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
        {
            // do nothing
        }

        /// <summary>
        /// During schema export operations, inserts annotations into the schema for non-null return values.
        /// </summary>
        /// <param name="clrType">The CLR type to be replaced.</param>
        /// <param name="dataContractType">The data contract type to be annotated.</param>
        /// <returns>
        /// An object that represents the annotation to be inserted into the XML schema definition.
        /// </returns>
        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            return null;
        }

        /// <summary>
        /// During schema export operations, inserts annotations into the schema for non-null return values.
        /// </summary>
        /// <param name="memberInfo">A <see cref="T:System.Reflection.MemberInfo"/> that describes the member.</param>
        /// <param name="dataContractType">A <see cref="T:System.Type"/>.</param>
        /// <returns>
        /// An object that represents the annotation to be inserted into the XML schema definition.
        /// </returns>
        public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
        {
            return null;
        }
    }
}
